﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace HeroldIT.Dnn.JQuery.UI
{
    /// <summary>
    /// Creates a jQuery UI Datepicker widget.
    /// </summary>
    [ParseChildren(true)]
    public class DatePicker : TextBox
    {
        #region static properties

        private static CultureInfo CurrentUICulture
        {
            get
            {
                return CultureInfo.CurrentUICulture.IsNeutralCulture
                           ? CultureInfo.CurrentCulture
                           : CultureInfo.CurrentUICulture;
            }
        }

        #endregion

        #region properties

        /// <summary>
        /// Gets or sets the name of the theme to use for the control.
        /// </summary>
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string ThemeName { get; set; }

        /// <summary>
        /// Gets or sets the z-index to apply to the Datepicker's popup.
        /// </summary>
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(0)]
        public int ZIndex { get; set; }

        /// <summary>
        /// Gets or sets whether to automatically register jQuery UI on the page.
        /// </summary>
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool AutoRegister { get; set; }

        #endregion

        #region jQuery UI settings properties

        /// <summary>
        /// Gets or sets the jQuery selector for another field that is to be updated
        /// with the selected date from the datepicker. Use the <see cref="AltFormat"/>
        /// setting to change the format of the date within this field. Leave as blank
        /// for no alternate field.
        /// </summary>
        [Setting("altField")]
        [IDReferenceProperty(typeof (ITextControl))]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public string AltField { get; set; }

        /// <summary>
        /// Gets or sets the dateFormat to be used for the <see cref="AltField"/> option.
        /// This allows one date format to be shown to the user for selection purposes,
        /// while a different format is actually sent behind the scenes. For a full list
        /// of the possible formats see the formatDate javascript function.
        /// </summary>
        [Setting("altFormat")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public string AltFormat { get; set; }

        /// <summary>
        /// Gets or sets the text to display after each date field, e.g. to show the
        /// required format.
        /// </summary>
        [Setting("appendText")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string AppendText { get; set; }

        /// <summary>
        /// Gets or sets the URL for the popup button image. If set, <see cref="ButtonText"/>
        /// becomes the alt value and is not directly displayed.
        /// </summary>
        [Setting("buttonImage")]
        [UrlProperty]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string ButtonImage { get; set; }

        /// <summary>
        /// Set to true to place an image after the field to use as the trigger without
        /// it appearing on a button.
        /// </summary>
        [Setting("buttonImageOnly")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public bool ButtonImageOnly { get; set; }

        private string buttonText = "...";

        /// <summary>
        /// Gets or sets the text to display on the trigger button. Use in conjunction
        /// with <see cref="ShowOn" /> equal to 'button' or 'both'.
        /// </summary>
        [Setting("buttonText")]
        [DefaultValue("...")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string ButtonText
        {
            get { return this.buttonText; }
            set { this.buttonText = value; }
        }

        /// <summary>
        /// Allows you to change the month by selecting from a drop-down list. You can
        /// enable this feature by setting the property to true.
        /// </summary>
        [Setting("changeMonth")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public bool ChangeMonth { get; set; }

        /// <summary>
        /// Allows you to change the year by selecting from a drop-down list. You can
        /// enable this feature by setting the property to true.
        /// </summary>
        [Setting("changeYear")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public bool ChangeYear { get; set; }

        private string closeText = "Done";

        /// <summary>
        /// Gets or sets the text to display for the close link. This property is one of
        /// the regionalisation properties. Use <see cref="ShowButtonPanel"/> to display
        /// this button.
        /// </summary>
        [Setting("closeText")]
        [DefaultValue("Done")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string CloseText
        {
            get { return this.closeText; }
            set { this.closeText = value; }
        }

        private bool constrainInput = true;

        /// <summary>
        /// <c>true</c> if the input field is constrained to the current date format.
        /// </summary>
        [Setting("constrainInput")]
        [DefaultValue(true)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public bool ConstrainInput
        {
            get { return this.constrainInput; }
            set { this.constrainInput = value; }
        }

        private string currentText = "Today";

        /// <summary>
        /// Gets or sets the text to display for the current day link. This property is
        /// one of the regionalisation properties. Use <see cref="ShowButtonPanel"/> to
        /// display this button.
        /// </summary>
        [Setting("currentText")]
        [DefaultValue("Today")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string CurrentText
        {
            get { return this.currentText; }
            set { this.currentText = value; }
        }

        private string dateFormat = "mm/dd/yy";

        /// <summary>
        /// Gets or sets the format for parsed and displayed dates. This properties is
        /// one of the regionalisation properties. For a full list of the possible formats
        /// see the formatDate javascript function.
        /// </summary>
        [Setting("dateFormat")]
        [DefaultValue("mm/dd/yy")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string DateFormat
        {
            get { return this.dateFormat; }
            set { this.dateFormat = value; }
        }

        private Dictionary<DayOfWeek, string> dayNames;

        /// <summary>
        /// Gets the dictionary of long day names, starting from Sunday, for use as requested
        /// via the <see cref="DateFormat"/> property. They also appear as popup hints when
        /// hovering over the corresponding column headings. This property is one of the
        /// regionalisation properties.
        /// </summary>
        [Setting("dayNames", DictionarySerializationMode = DictionarySerializationMode.ValuesOnly)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public Dictionary<DayOfWeek, string> DayNames
        {
            get
            {
                if (null == this.dayNames)
                {
                    this.dayNames = new Dictionary<DayOfWeek, string>(7);
                    for (int i = (int)DayOfWeek.Sunday; i <= (int)DayOfWeek.Saturday; i++)
                    {
                        DayOfWeek dayOfWeek = (DayOfWeek)i;
                        this.dayNames[dayOfWeek] = CurrentUICulture.DateTimeFormat.GetDayName(dayOfWeek);
                    }
                }
                return this.dayNames;
            }
        }

        private Dictionary<DayOfWeek, string> dayNamesMin;

        /// <summary>
        /// Gets the dictionary of minimised day names, starting from Sunday, for use as
        /// column headers within the datepicker. This property is one of the regionalisation
        /// properties.
        /// </summary>
        [Setting("dayNamesMin", DictionarySerializationMode = DictionarySerializationMode.ValuesOnly)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public Dictionary<DayOfWeek, string> DayNamesMin
        {
            get
            {
                if (null == this.dayNamesMin)
                {
                    this.dayNamesMin = new Dictionary<DayOfWeek, string>(7);
                    for (int i = (int)DayOfWeek.Sunday; i <= (int)DayOfWeek.Saturday; i++)
                    {
                        DayOfWeek dayOfWeek = (DayOfWeek)i;
                        this.dayNamesMin[dayOfWeek] = CurrentUICulture.DateTimeFormat.GetShortestDayName(dayOfWeek);
                    }
                }
                return this.dayNamesMin;
            }
        }

        private Dictionary<DayOfWeek, string> dayNamesShort;

        /// <summary>
        /// Gets the dictionary of abbreviated day names, starting from Sunday, for use as
        /// requested via the <see cref="DateFormat"/> property. This property is one of the
        /// regionalisation properties.
        /// </summary>
        [Setting("dayNamesShort", DictionarySerializationMode = DictionarySerializationMode.ValuesOnly)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public Dictionary<DayOfWeek, string> DayNamesShort
        {
            get
            {
                if (null == this.dayNamesShort)
                {
                    this.dayNamesShort = new Dictionary<DayOfWeek, string>(7);
                    for (int i = (int)DayOfWeek.Sunday; i <= (int)DayOfWeek.Saturday; i++)
                    {
                        DayOfWeek dayOfWeek = (DayOfWeek)i;
                        this.dayNamesShort[dayOfWeek] = CurrentUICulture.DateTimeFormat.GetAbbreviatedDayName(dayOfWeek);
                    }
                }
                return this.dayNamesShort;
            }
        }

        /// <summary>
        /// Gets or sets the date to highlight on first opening if the field is blank.
        /// If not set, the current day will be highlighted.
        /// </summary>
        [Setting("defaultDate", DateTimeSerializationMode = DateTimeSerializationMode.DateOnly)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public DateTime? DefaultDate { get; set; }

        private string duration = "normal";

        /// <summary>
        /// Gets or sets the speed at which the datepicker appears. It may be a time
        /// in milliseconds, a string representing one of the three predefined speeds
        /// ("slow", "normal", "fast"), or the empty string for immediately.
        /// </summary>
        [Setting("duration")]
        [DefaultValue("normal")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string Duration
        {
            get { return this.duration; }
            set { this.duration = value; }
        }

        /// <summary>
        /// Gets or sets the first day of the week: Sunday is 0, Monday is 1, ... This
        /// property is one of the regionalisation properties.
        /// </summary>
        [Setting("firstDay")]
        [DefaultValue(0)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public int FirstDay { get; set; }

        /// <summary>
        /// If <c>true</c>, the current day link moves to the currently selected
        /// date instead of today.
        /// </summary>
        [Setting("gotoCurrent")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public bool GotoCurrent { get; set; }

        /// <summary>
        /// Gets or sets whether the previous and next links are hidden when not applicable
        /// (see <see cref="MinDate"/>/<see cref="MaxDate"/>). If set to <c>false</c>
        /// (default) the links are only disabled.
        /// </summary>
        [Setting("hideIfNoPrevNext")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public bool HideIfNoPrevNext { get; set; }

        /// <summary>
        /// <c>true</c> if the current language is drawn from right to left. This
        /// property is one of the regionalisation properties.
        /// </summary>
        [Setting("isRTL")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public bool IsRTL { get; set; }

        /// <summary>
        /// Gets or sets a maximum selectable date. If <c>null</c> (default) no limit
        /// is set.
        /// </summary>
        [Setting("maxDate", DateTimeSerializationMode = DateTimeSerializationMode.DateOnly)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public DateTime? MaxDate { get; set; }

        /// <summary>
        /// Gets or sets a minimum selectable date. If <c>null</c> (default) no limit
        /// is set.
        /// </summary>
        [Setting("minDate", DateTimeSerializationMode = DateTimeSerializationMode.DateOnly)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public DateTime? MinDate { get; set; }

        private Dictionary<int, string> monthNames;

        /// <summary>
        /// Gets the dictionary of full month names, as used in the month header on each
        /// datepicker and as requested via the <see cref="DateFormat"/> property. This
        /// property is one of the regionalisation properties.
        /// </summary>
        [Setting("monthNames", DictionarySerializationMode = DictionarySerializationMode.ValuesOnly)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public Dictionary<int, string> MonthNames
        {
            get
            {
                if (null == this.monthNames)
                {
                    this.monthNames = new Dictionary<int, string>(12);
                    for (int i = 1; i <= 12; i++)
                    {
                        monthNames[i] = CurrentUICulture.DateTimeFormat.GetMonthName(i);
                    }
                }
                return this.monthNames;
            }
        }

        private Dictionary<int, string> monthNamesShort;

        /// <summary>
        /// Gets the dictionary of abbreviated month names, for use as requested via the
        /// <see cref="DateFormat"/> property. This property is one of the regionalisation
        /// properties.
        /// </summary>
        [Setting("monthNamesShort", DictionarySerializationMode = DictionarySerializationMode.ValuesOnly)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public Dictionary<int, string> MonthNamesShort
        {
            get
            {
                if (null == this.monthNamesShort)
                {
                    this.monthNamesShort = new Dictionary<int, string>(12);
                    for (int i = 1; i <= 12; i++)
                    {
                        monthNamesShort[i] = CurrentUICulture.DateTimeFormat.GetAbbreviatedMonthName(i);
                    }
                }
                return this.monthNamesShort;
            }
        }

        /// <summary>
        /// When <c>true</c> the formatDate javascript function is applied to the
        /// <see cref="PrevText"/>, <see cref="NextText"/>, and <see cref="CurrentText"/>
        /// values before display, allowing them to display the target month names for example.
        /// </summary>
        [Setting("navigationAsDateFormat")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public bool NavigationAsDateFormat { get; set; }

        private string nextText = "Next";

        /// <summary>
        /// Gets or sets the text to display for the next month link. This property is one
        /// of the regionalisation properties. With the standard ThemeRoller styling, this
        /// value is replaced by an icon.
        /// </summary>
        [Setting("nextText")]
        [DefaultValue("Next")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string NextText
        {
            get { return this.nextText; }
            set { this.nextText = value; }
        }

        private int numberOfMonths = 1;

        /// <summary>
        /// Gets or sets how many months to show at once.
        /// </summary>
        [Setting("numberOfMonths")]
        [DefaultValue(1)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public int NumberOfMonths
        {
            get { return this.numberOfMonths; }
            set { this.numberOfMonths = value; }
        }

        private string prevText = "Prev";

        /// <summary>
        /// Gets or sets the text to display for the previous month link. This property
        /// is one of the regionalisation properties. With the standard ThemeRoller styling,
        /// this value is replaced by an icon.
        /// </summary>
        [Setting("prevText")]
        [DefaultValue("Prev")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string PrevText
        {
            get { return this.prevText; }
            set { this.prevText = value; }
        }

        private string shortYearCutoff = "+10";

        /// <summary>
        /// Gets or sets the cutoff year for determining the century for a date (used in
        /// conjunction with dateFormat 'y'). If a numeric value (0-99) is provided then
        /// this value is used directly. If a string value is provided then it is converted
        /// to a number and added to the current year. Once the cutoff year is calculated,
        /// any dates entered with a year value less than or equal to it are considered to
        /// be in the current century, while those greater than it are deemed to be in the
        /// previous century.
        /// </summary>
        [Setting("shortYearCutoff")]
        [DefaultValue("+10")]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public string ShortYearCutoff
        {
            get { return this.shortYearCutoff; }
            set { this.shortYearCutoff = value; }
        }

        private string showAnim = "show";

        /// <summary>
        /// Gets or sets the name of the animation used to show/hide the datepicker. Use
        /// 'show' (the default), 'slideDown', 'fadeIn', or any of the show/hide jQuery UI
        /// effects.
        /// </summary>
        [Setting("showAnim")]
        [DefaultValue("show")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string ShowAnim
        {
            get { return this.showAnim; }
            set { this.showAnim = value; }
        }

        /// <summary>
        /// Gets or sets whether to show the button panel.
        /// </summary>
        [Setting("showButtonPanel")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public bool ShowButtonPanel { get; set; }

        /// <summary>
        /// Gets or sets where in a multi-month display the current month shows, starting
        /// from 0 at the top/left.
        /// </summary>
        [Setting("showCurrentAtPos")]
        [DefaultValue(0)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public int ShowCurrentAtPos { get; set; }

        /// <summary>
        /// Gets or sets whether to show the month after the year in the header.
        /// </summary>
        [Setting("showMonthAfterYear")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public bool ShowMonthAfterYear { get; set; }

        private DatePickerShowOn showOn = DatePickerShowOn.Focus;

        /// <summary>
        /// Gets or sets the event on which the Datepicker appears. <c>"focus"</c>
        /// (default) makes it appear, when the field is focussed, <c>"button"</c>
        /// makes it appear only when a button is clicked, <c>"both"</c> makes it
        /// appear when either event takes place.
        /// </summary>
        [Setting("showOn")]
        [DefaultValue(DatePickerShowOn.Focus)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public DatePickerShowOn ShowOn
        {
            get { return this.showOn; }
            set { this.showOn = value; }
        }

        private Dictionary<string, object> showOptions;

        /// <summary>
        /// If using one of the jQuery UI effects for <see cref="ShowAnim"/>, gets or sets
        /// additional settings for that animation.
        /// </summary>
        [Setting("showOptions", DictionarySerializationMode = DictionarySerializationMode.Normal)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public Dictionary<string, object> ShowOptions
        {
            get
            {
                if (null == this.showOptions)
                {
                    this.showOptions = new Dictionary<string, object>();
                }
                return this.showOptions;
            }
        }

        /// <summary>
        /// Gets or sets whether to display dates in other months (non-selectable) at the
        /// start or end of the current month.
        /// </summary>
        [Setting("showOtherMonths")]
        [DefaultValue(false)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public bool ShowOtherMonths { get; set; }

        private int stepMonths = 1;

        /// <summary>
        /// Gets or sets how many months to move when clicking the Previous/Next links.
        /// </summary>
        [Setting("stepMonths")]
        [DefaultValue(1)]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public int StepMonths
        {
            get { return this.stepMonths; }
            set { this.stepMonths = value; }
        }

        private string yearRange = "-10:+10";

        /// <summary>
        /// Gets or sets the range of years displayed in the year drop-down: either relative
        /// to current year (-nn:+nn) or absolute (nnnn:nnnn). The default is <c>"-10:+10"</c>.
        /// </summary>
        [Setting("yearRange")]
        [DefaultValue("-10:+10")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public string YearRange
        {
            get { return this.yearRange; }
            set { this.yearRange = value; }
        }

        /// <summary>
        /// Gets or sets the locale to use for the regionalisation properties. If specified,
        /// the values given in the regionalisation properties are ignored.
        /// </summary>
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string Locale { get; set; }

        #endregion

        #region jQuery client events

        /// <summary>
        /// Defines the client events for the <see cref="DatePicker"/> control.
        /// </summary>
        public sealed class ClientEventDefinitions
        {
            #region properties

            [Setting("beforeShow")]
            internal ClientEventHandler BeforeShowHandler
            {
                get { return this.BeforeShow == null ? null : new ClientEventHandler("input,inst", this.BeforeShow); }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that takes an input field and the current
            /// datepicker instance and returns an options object to update the datepicker with.
            /// It is called just before the datepicker is displayed. The names of the
            /// arguments to the function are <c>input</c> and <c>inst</c> respectively.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string BeforeShow
            {
                get;
                set;
            }

            [Setting("beforeShowDay")]
            internal ClientEventHandler BeforeShowDayHandler
            {
                get
                {
                    return this.BeforeShowDay == null ? null : new ClientEventHandler("date", this.BeforeShowDay);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that takes a date as a parameter and must
            /// return an array with [0] equal to true/false indicating whether or not this
            /// date is selectable, [1] equal to a CSS class name(s) or '' for the default
            /// presentation and [2] an optional popup tooltip for this date. It is called
            /// for each day in the datepicker before is it displayed. The name of the
            /// argument to the function is <c>date</c>.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string BeforeShowDay
            {
                get;
                set;
            }

            [Setting("onChangeMonthYear")]
            internal ClientEventHandler OnChangeMonthYearHandler
            {
                get
                {
                    return this.OnChangeMonthYear == null ? null : new ClientEventHandler("year,month,inst", this.OnChangeMonthYear);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is executed when the datepicker moves
            /// to a new month and/or year. The function receives the selected year, month and
            /// the datepicker instance as parameters. <c>this</c> refers to the associated
            /// input field. The names of the arguments to the function are <c>year</c>,
            /// <c>month</c>, and <c>inst</c> respectively.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string OnChangeMonthYear
            {
                get;
                set;
            }

            [Setting("onClose")]
            internal ClientEventHandler OnCloseHandler
            {
                get
                {
                    return this.OnClose == null ? null : new ClientEventHandler("date,inst", this.OnClose);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is executed when the datepicker is
            /// closed, whether or not a date is selected. The function receives the selected
            /// date as a Date and the datepicker instance as parameters. <c>this</c> refers
            /// to the associated input field. The names of the arguments to the function are
            /// <c>date</c> and <c>inst</c> respectively.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string OnClose
            {
                get;
                set;
            }

            [Setting("onSelect")]
            internal ClientEventHandler OnSelectHandler
            {
                get
                {
                    return this.OnSelect == null ? null : new ClientEventHandler("dateStr,inst", this.OnSelect);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is executed when the datepicker is
            /// selected. The function receives the selected date(s) as text and the datepicker
            /// instance as parameters. <c>this</c> refers to the associated input field. The
            /// names of the arguments to the function are <c>dateStr</c> and <c>inst</c>
            /// respectively.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string OnSelect
            {
                get;
                set;
            }

            #endregion

            #region constructors

            /// <summary>
            /// Creates a new <see cref="ClientEventDefinitions"/> instance.
            /// </summary>
            internal ClientEventDefinitions()
            {
            }

            #endregion
        }

        private readonly ClientEventDefinitions clientEvents = new ClientEventDefinitions();

        /// <summary>
        /// Holds the definitions for the client events.
        /// </summary>
        [SettingGroup]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public ClientEventDefinitions ClientEvents
        {
            get
            {
                return this.clientEvents;
            }
        }

        #endregion

        #region events

        /// <summary>
        /// This event is raised, just before the control applies its javascript
        /// settings to the initialization code. You can use it to manipulate the
        /// settings dictionary.
        /// </summary>
        /// <remarks>
        /// Be careful when using this event. It allows you to reach deeply into
        /// the internal workings of the control, possibly breaking functionality.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [Localizable(false)]
        [Category("Behavior")]
        public event EventHandler<SettingsEventArgs> ApplyingSettings;

        /// <summary>
        /// Fires the <see cref="ApplyingSettings"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SettingsEventArgs"/> passed to the event.</param>
        protected virtual void OnApplyingSettings(SettingsEventArgs e)
        {
            if (null != this.ApplyingSettings)
            {
                this.ApplyingSettings(this, e);
            }
        }

        #endregion

        #region page lifecycle

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            if (this.AutoRegister)
            {
                this.Page.RegisterJQueryUI();
            }

            base.OnInit(e);
        }

        /// <summary>
        /// Registers client script for generating postback events prior to rendering on the client, if <see cref="P:System.Web.UI.WebControls.TextBox.AutoPostBack"/> is true.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (this.Visible)
            {
                string themeName = this.ResolveThemeName();
                var settings = SettingSerializer.FetchSettings(this);
                if (!String.IsNullOrEmpty(this.Locale))
                {
                    if (!this.Locale.Equals("en"))
                    {
                        this.Page.RegisterJQueryUILocale("datepicker", this.Locale);
                    }
                    settings.Remove("closeText");
                    settings.Remove("currentText");
                    settings.Remove("dateFormat");
                    settings.Remove("dayNames");
                    settings.Remove("dayNamesShort");
                    settings.Remove("dayNamesMin");
                    settings.Remove("firstDay");
                    settings.Remove("isRTL");
                    settings.Remove("monthNames");
                    settings.Remove("monthNamesShort");
                    settings.Remove("nextText");
                    settings.Remove("prevText");
                }

                // handle theme wrapping
                // NOTE: This is somewhat of a hack due to lack of other options. Keep an eye on this, as it may break sooner rather than later!
                if (!String.IsNullOrEmpty(themeName))
                {
                    settings["beforeShow"] =
                        new ClientEventHandler(this.ClientEvents.BeforeShowHandler ?? new ClientEventHandler("input,inst", ""),
                                               String.Format(CultureInfo.InvariantCulture,
                                                             "jQuery(inst.dpDiv).wrap('<div class=\"jquery-ui-{0}\"></div>'){1};",
                                                             themeName,
                                                             this.ZIndex != 0
                                                                 ? ".css({zIndex:" + this.ZIndex + "})"
                                                                 : ""));
                    settings["onClose"] =
                        new ClientEventHandler(this.ClientEvents.OnCloseHandler ?? new ClientEventHandler("input,inst", ""),
                                               "jQuery(inst.dpDiv).parent().replaceWith(jQuery(inst.dpDiv));");
                }
                // process 'altField' to make it use the referenced control's ClientID if available.
                if (settings.ContainsKey("altField"))
                {
                    Control altField = this.NamingContainer.FindControl((string) settings["altField"]);
                    if (null != altField)
                    {
                        settings["altField"] = altField.ClientID;
                    }
                }
                // apply custom setting manipulation logic
                this.OnApplyingSettings(new SettingsEventArgs(settings));

                // make it json and register the DocumentReadyBlock
                string settingsJson = SettingSerializer.Serialize(settings);
                if (!String.IsNullOrEmpty(this.Locale))
                {
                    string localeJson = String.Format(CultureInfo.InvariantCulture,
                                                      "jQuery.datepicker.regional['{0}']",
                                                      this.Locale);
                    if (String.IsNullOrEmpty(settingsJson))
                    {
                        settingsJson = localeJson;
                    }
                    else
                    {
                        settingsJson = String.Format(CultureInfo.InvariantCulture,
                                                     "jQuery.extend({0}, {1})",
                                                     settingsJson,
                                                     localeJson);
                    }
                }
                this.Page.RegisterJQueryOnDocumentReadyBlock(
                    String.Format(
                        CultureInfo.InvariantCulture,
                        "$('#{0}').datepicker({1})",
                        this.ClientID,
                        settingsJson
                        ));
            }
        }

        /// <summary>
        /// Renders the <see cref="DatePicker"/> control to the specified <see cref="T:System.Web.UI.HtmlTextWriter"/> object.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> that receives the rendered output.</param>
        protected override void Render(HtmlTextWriter writer)
        {
            string themeName = this.ResolveThemeName();
            if (!String.IsNullOrEmpty(themeName))
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Class, "jquery-ui-" + themeName);
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
            }

            base.Render(writer);

            if (!String.IsNullOrEmpty(themeName))
            {
                writer.RenderEndTag();
            }
        }

        private string ResolveThemeName()
        {
            string themeName = this.ThemeName;
            if (String.IsNullOrEmpty(themeName))
            {
                themeName = this.Page.GetDefaultJQueryUITheme();
            }
            return themeName;
        }

        #endregion
    }
}
